# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: api\handlers\frontend\child_users.py
__author__ = "sanyi"
import datetime, logging, uuid, sqlalchemy.exc, sqlalchemy.sql.expression
from dateutil.tz import tzlocal
from sqlalchemy import select as sql_select
from sqlalchemy import func as sql_func
from api.classes.frontend import *
from api.handlers.frontend import FrontendResourceHandler
from db import Connection
from db.data.users import require_master_user
from db.tables.events import create_event, EventResources
from db.tables.groups import GroupRow
from db.tables.user2group import User2GroupTable, User2GroupRow
from db.tables.users import UserRow, UsersTable
from server.http_responses.http_errors import (
    BadApiRequest,
    Http404,
    Http403,
    ApiErrors,
    Http409,
)
from server.http_responses.http_status_codes import Http204, Http201
from helpers.licensing.features import Features
from helpers.emails import is_email_banned
from helpers.setup_types import *
from settings_provider import settings
from helpers.account import (
    require_password_is_not_reused,
    update_account_security,
    get_account_security_settings,
    reset_ui_sessions,
)

logger = logging.getLogger("frontend_api_resources.Users")


class ChildUsers(FrontendResourceHandler):
    def get_users(self, l, c=None, **_):
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        limit = min(l, 100) if l is not None else 100
        c = int(c) if c is not None else 0
        cursor = c
        query = (
            sql_select((UserRow,))
            .where(UserRow.owner_id == self.request_user["user_id"])
            .where(UserRow.deleted_at.is_(None))
            .limit(limit)
            .offset(cursor)
            .order_by(UserRow.user_id)
        )
        users = []
        with Connection(self.current_shard_name) as (connection):
            for user in connection.execute(query).fetchall():
                user = dict(user)
                if user["password"]:
                    user["password"] = "password set"
                users.append(ClsChildUser(ignore_null_values=True, **user))

        l = len(users)
        if l >= limit:
            next_cursor = cursor + l
        else:
            next_cursor = None
        return ClsChildUserListResponse(
            users=users,
            pagination=ClsPagination(previous_cursor=cursor, next_cursor=next_cursor),
        )

    def get_user(self, user_id, **_):
        """
        Get child user info
        :param user_id:
        :param _:
        :return:
        """
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        query = (
            sql_select((UserRow,))
            .where(UserRow.owner_id == self.request_user["user_id"])
            .where(UserRow.deleted_at.is_(None))
            .where(UserRow.user_id == user_id)
        )
        with Connection(self.current_shard_name) as (connection):
            user = connection.execute(query).fetchone()
        if not user:
            return Http404()
        else:
            user = dict(user)
            if user["password"]:
                user["password"] = "password set"
            return ClsChildUser(ignore_null_values=True, **user)

    def add_user(self, body, **_):
        """
        Request: patch
        :param body:
        :param _:
        :return:
        """
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        user = ClsChildUser(**body)
        email = user.email.lower()
        if has_feature(SetupFeatures.EMAIL_FILTERING):
            if is_email_banned(email):
                raise Http409("unsupported email domain")
        user.user_id = str(uuid.uuid4())
        password = user.get("password")
        if not password:
            raise BadApiRequest("No password set")
        ph = settings["password_hashing_strategy"]
        child_user_details = dict(
            user_id=user.user_id,
            owner_id=self.request_user["user_id"],
            email=email,
            first_name=user.first_name,
            last_name=user.last_name,
            role=user.role,
            access_all_groups=user.get("access_all_groups", False),
            enabled=user.get("enabled", True),
            password=ph(password, settings.get("password_hash_salt")),
        )
        add_user_query = UsersTable.insert().values(**child_user_details)
        event_query = create_event(
            "child_account_created",
            self.request_user["user_id"],
            data=user.to_dict(),
            resource_type=EventResources.child_user,
            resource_id=user.user_id,
            shard=self.current_shard_name,
        )
        if self.license.max_child_user_count == 0:
            raise Http403("can't create child users")
        with Connection(self.current_shard_name) as (connection):
            max_child_user_count = self.license.max_child_user_count
            if is_setup_type_aws():
                if max_child_user_count is None:
                    max_child_user_count = 100
            if max_child_user_count is not None:
                q = (
                    sql_select((sql_func.count(UserRow.user_id),))
                    .where(UserRow.owner_id == self.request_user["user_id"])
                    .where(UserRow.deleted_at.is_(None))
                )
                c = connection.execute(q).scalar()
                if c:
                    if c >= max_child_user_count:
                        raise Http403(
                            "Max users reached", code=ApiErrors.MAX_CHILD_USERS_REACHED
                        )
            try:
                connection.execute(add_user_query)
            except sqlalchemy.exc.IntegrityError as e:
                if '"ix_users_unique_active_email"' in str(e):
                    raise BadApiRequest("Email already exist")
                raise

            connection.execute(event_query)
        return Http201(location=self.base_path + "/users/" + user.user_id, body=user)

    def update_user(self, user_id, body, **_):
        """
        updates child user via a patch
        :param user_id:
        :param body:
        :param _:
        :return:
        """
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        changes = ClsChildUser(
            ignored_required_keys=True, ignore_default_values=True, **body
        )
        update = dict()
        with Connection(self.current_shard_name) as (db):
            q = (
                sql_select((UserRow,))
                .where(UserRow.owner_id == self.request_user_id)
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.user_id == user_id)
            )
            user = db.execute(q).fetchone()
            if not user:
                raise Http404()
            if hasattr(changes, "email"):
                email = changes.email.lower()
                if email != user.email:
                    if has_feature(SetupFeatures.EMAIL_FILTERING):
                        if is_email_banned(email):
                            raise Http409("invalid email")
                    update["email"] = email
            if hasattr(changes, "first_name"):
                if changes.first_name != user.first_name:
                    update["first_name"] = changes.first_name
            if hasattr(changes, "last_name"):
                if changes.last_name != user.last_name:
                    update["last_name"] = changes.last_name
            if hasattr(changes, "role"):
                if changes.role != user.role:
                    update["role"] = changes.role
            if hasattr(changes, "enabled"):
                if changes.enabled != user.enabled:
                    update["enabled"] = changes.enabled
            if hasattr(changes, "access_all_groups"):
                if changes.access_all_groups != user.access_all_groups:
                    update["access_all_groups"] = changes.access_all_groups
            if hasattr(changes, "password"):
                ph = settings["password_hashing_strategy"]
                new_password = ph(changes.password, settings.get("password_hash_salt"))
                if new_password == user.password:
                    if user.pwd_history:
                        raise Http409(
                            "password reused", code=ApiErrors.PASSWORD_REUSE_ERROR
                        )
                    else:
                        require_password_is_not_reused(
                            db,
                            user.user_id,
                            user.pwd_history,
                            new_password,
                            user.password,
                        )
                        if user["pwd_max_age"]:
                            update["pwd_expires"] = datetime.datetime.now(tzlocal()) + (
                                datetime.timedelta(days=user["pwd_max_age"])
                            )
                        else:
                            update["pwd_expires"] = None
                        update["password"] = new_password
                    if update:
                        query = (
                            (UsersTable.update(values=update))
                            .where(UserRow.owner_id == self.request_user["user_id"])
                            .where(UserRow.deleted_at.is_(None))
                            .where(UserRow.user_id == user_id)
                        )
                        db.execute(query)
                        event = create_event(
                            "child_account_modified",
                            self.request_user_id,
                            data=update,
                            resource_type=EventResources.child_user,
                            resource_id=user_id,
                            shard=self.current_shard_name,
                        )
                        db.execute(event)
                        if "password" in update or "email" in update:
                            reset_ui_sessions(db, user["user_id"])
        return Http204()

    def get_account_security_settings(self, user_id, **_):
        require_master_user(self.request_user)
        self.license.require(Features.MULTI_USER)
        with Connection(self.current_shard_name) as (db):
            q = (
                sql_select((UserRow,))
                .where(UserRow.owner_id == self.request_user_id)
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.user_id == user_id)
            )
            user = db.execute(q).fetchone()
            if not user:
                raise Http404()
            return get_account_security_settings(dict(user))

    def modify_account_security_settings(self, user_id, body, **_):
        require_master_user(self.request_user)
        self.license.require(Features.MULTI_USER)
        data = ClsAccountSecurity(**body)
        with Connection(self.current_shard_name) as (db):
            q = (
                sql_select((UserRow,))
                .where(UserRow.owner_id == self.request_user_id)
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.user_id == user_id)
            )
            user = db.execute(q).fetchone()
            if not user:
                raise Http404()
            user = dict(user)
            update_account_security(db, user, data)
        return Http204()

    def otp_reset(self, user_id, **_):
        require_master_user(self.request_user)
        self.license.require(Features.MULTI_USER)
        with Connection(self.current_shard_name) as (db):
            q = (
                (UsersTable.update(values=dict(otp=None)))
                .where(UserRow.owner_id == self.request_user_id)
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.user_id == user_id)
            )
            result = db.execute(q)
            if result.rowcount == 0:
                return Http404()
        return Http204()

    def remove_user(self, user_id, **_):
        require_master_user(self.request_user)
        self.license.require(Features.MULTI_USER)
        event = create_event(
            "child_account_deleted",
            self.request_user["user_id"],
            resource_type=EventResources.child_user,
            resource_id=user_id,
            shard=self.current_shard_name,
        )
        with Connection(self.current_shard_name) as (connection):
            query = (
                (
                    UsersTable.update(
                        values=dict(deleted_at=datetime.datetime.now(tzlocal()))
                    )
                )
                .where(UserRow.owner_id == self.request_user["user_id"])
                .where(UserRow.deleted_at.is_(None))
                .where(UserRow.user_id == user_id)
            )
            result = connection.execute(query)
            if result.rowcount != 0:
                connection.execute(event)
        return Http204()

    def get_access(self, user_id, **_):
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        user_query = (
            sql_select((UserRow.access_all_groups,))
            .where(UserRow.user_id == user_id)
            .where(UserRow.deleted_at.is_(None))
            .where(UserRow.owner_id == self.request_user["user_id"])
        )
        access_query = sql_select((User2GroupRow.group_id,)).where(
            User2GroupRow.user_id == user_id
        )
        access = ClsUserAccess(access_all_groups=False, group_id_list=[])
        with Connection(self.current_shard_name) as (connection):
            user = connection.execute(user_query).fetchone()
            if not user:
                return Http404()
            access.access_all_groups = user.access_all_groups
            if not access.access_all_groups:
                for group_access in connection.execute(access_query).fetchall():
                    access.group_id_list.append(group_access.group_id)

        return access

    def set_access(self, user_id, body, **_):
        self.license.require(Features.MULTI_USER)
        require_master_user(self.request_user)
        owner_id = self.request_user["user_id"]
        access = ClsUserAccess(**body)
        if not access.is_complete():
            raise AssertionError
        access.group_id_list = list(set(access.group_id_list))
        delete_query = (
            User2GroupTable.delete()
            .where(User2GroupRow.user_id == user_id)
            .where(User2GroupRow.owner_id == owner_id)
        )
        user_query = (
            sql_select((UserRow.user_id, UserRow.access_all_groups))
            .where(UserRow.owner_id == owner_id)
            .where(UserRow.deleted_at.is_(None))
            .where(UserRow.user_id == user_id)
        )
        update_group_access_all = (
            UsersTable.update(values={"access_all_groups": access.access_all_groups})
        ).where(UserRow.user_id == user_id)
        with Connection(self.current_shard_name, create_transactions=True) as (
            connection
        ):
            user = connection.execute(user_query).fetchone()
            if not user:
                return Http403()
            if access.access_all_groups:
                if user.access_all_groups:
                    return Http204()
                connection.execute(delete_query)
                connection.execute(update_group_access_all)
                return Http204()
            if user.access_all_groups:
                connection.execute(update_group_access_all)
            connection.execute(delete_query)
            if access.group_id_list:
                src_query = (
                    sql_select(
                        (
                            GroupRow.group_id,
                            sqlalchemy.sql.expression.bindparam("user_id", user_id),
                            sqlalchemy.sql.expression.bindparam("owner_id", owner_id),
                        )
                    )
                    .where(GroupRow.owner_id == self.request_user["user_id"])
                    .where(GroupRow.group_id.in_(access.group_id_list))
                )
                query = User2GroupTable.insert().from_select(
                    (
                        User2GroupRow.group_id,
                        User2GroupRow.user_id,
                        User2GroupRow.owner_id,
                    ),
                    src_query,
                )
                connection.execute(query)
            return Http204()
